Skip to content

ROX-30296: track POSIX ACL changes via inode_set_acl LSM hook#878

Open
Stringy wants to merge 12 commits into
mainfrom
giles/ROX-30296-track-acl
Open

ROX-30296: track POSIX ACL changes via inode_set_acl LSM hook#878
Stringy wants to merge 12 commits into
mainfrom
giles/ROX-30296-track-acl

Conversation

@Stringy

@Stringy Stringy commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Description

Adds ACL tracking via inode_set_acl hook. Due to lack of path info in this hook, it is only possible to monitor inode-tracked files (i.e. those on the host file system) this is similar to how xattrs are tracked.

An extension to the BPF checks.c file has been added, because this hook does not exist on older kernels (where ACLs were applied via xattrs, so we still have coverage on those old kernels, just through a different mechanism.)

Tests factor in this optional nature of the hook, and will only run when we know the kernel supports it.

Relies on stackrox/stackrox#21357

Checklist

  • Patch has a change log entry OR does not need one.
  • Investigated and inspected CI test results
  • Updated documentation accordingly

Automated testing

  • Added unit tests
  • Added integration tests
  • Added regression tests

If any of these don't apply, please comment below.

Testing Performed

Tested locally, with setfacl, and the integration tests.

Summary by CodeRabbit

  • New Features

    • Added support for tracking ACL-related file activity events, including ACL type and decoded entry details.
    • Exposed ACL event data in the test harness and event models.
  • Bug Fixes

    • Improved compatibility by skipping ACL tracking on systems that don’t support the required kernel hook.
    • Updated event filtering so ACL events can be included or excluded more flexibly in tests.
  • Tests

    • Added coverage for ACL write, removal, and ignored-path scenarios.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Enterprise

Run ID: 5b916de2-f11e-48d7-b345-c3ed9b660223

📥 Commits

Reviewing files that changed from the base of the PR and between 3a4a860 and 4be488a.

📒 Files selected for processing (16)
  • CHANGELOG.md
  • fact-ebpf/src/bpf/checks.c
  • fact-ebpf/src/bpf/events.h
  • fact-ebpf/src/bpf/main.c
  • fact-ebpf/src/bpf/types.h
  • fact-ebpf/src/lib.rs
  • fact/src/bpf/checks.rs
  • fact/src/bpf/mod.rs
  • fact/src/event/mod.rs
  • fact/src/metrics/kernel_metrics.rs
  • tests/event.py
  • tests/server.py
  • tests/test_acl.py
  • tests/test_xattr.py
  • tests/utils.py
  • third_party/stackrox
 ____________________________________________________________________________________________________________________________________________________________________________
< Good code is its own best documentation. As you're about to add a comment, ask yourself, 'How can I improve the code so that this comment isn't needed?' - Steve McConnell >
 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch giles/ROX-30296-track-acl

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

codecov-commenter commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 106 lines in your changes missing coverage. Please review.
✅ Project coverage is 31.17%. Comparing base (0116956) to head (4be488a).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
fact/src/event/mod.rs 0.00% 78 Missing ⚠️
fact/src/bpf/checks.rs 0.00% 17 Missing ⚠️
fact/src/bpf/mod.rs 0.00% 11 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #878      +/-   ##
==========================================
- Coverage   32.23%   31.17%   -1.06%     
==========================================
  Files          21       21              
  Lines        2736     2829      +93     
  Branches     2736     2829      +93     
==========================================
  Hits          882      882              
- Misses       1851     1944      +93     
  Partials        3        3              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Stringy Stringy force-pushed the giles/ROX-30296-track-acl branch 4 times, most recently from 46d84a9 to 11683ba Compare June 29, 2026 09:45
@Stringy Stringy force-pushed the giles/ROX-30296-track-acl branch from 67a3b12 to be7fb22 Compare June 30, 2026 12:28
@Stringy Stringy force-pushed the giles/ROX-30296-track-acl branch from eae45f0 to fc73ed7 Compare June 30, 2026 14:02
@Stringy Stringy marked this pull request as ready for review July 1, 2026 10:40
@Stringy Stringy requested a review from a team as a code owner July 1, 2026 10:40
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
fact/src/event/mod.rs (1)

538-555: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add the missing Chown equality arm.

While extending this manual matcher, FileData::Chown still falls through to _ => false, so two identical ownership-change events compare unequal.

Proposed fix
             (FileData::Unlink(this), FileData::Unlink(other)) => this == other,
             (FileData::Chmod(this), FileData::Chmod(other)) => this == other,
+            (FileData::Chown(this), FileData::Chown(other)) => this == other,
             (FileData::Rename(this), FileData::Rename(other)) => this == other,
             (FileData::SetXattr(this), FileData::SetXattr(other)) => this == other,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fact/src/event/mod.rs` around lines 538 - 555, The manual PartialEq
implementation for FileData is missing the FileData::Chown arm, so identical
ownership-change events currently fall through to false. Update the eq method in
FileData’s PartialEq match to add a Chown(this), Chown(other) branch that
compares the contained values the same way as the other variants, keeping the
fallback _ => false only for truly different variants.
🧹 Nitpick comments (1)
fact/src/bpf/checks.rs (1)

34-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider logging the underlying error before treating any load failure as "unsupported."

probe_hook returns false for missing programs, wrong program type, and load failures alike, discarding the error from prog.load(...). This is fine for genuinely unsupported kernels, but it also silently masks a real bug (e.g. a verifier failure in checks.c) as "capability not supported," making such regressions hard to diagnose.

♻️ Suggested diagnostic logging
     fn probe_hook(obj: &mut aya::Ebpf, prog_name: &str, hook: &str, btf: &Btf) -> bool {
         let Some(prog) = obj.program_mut(prog_name) else {
             return false;
         };
         let Ok(prog): Result<&mut Lsm, _> = prog.try_into() else {
             return false;
         };
-        prog.load(hook, btf).is_ok()
+        match prog.load(hook, btf) {
+            Ok(()) => true,
+            Err(e) => {
+                debug!("Failed to probe {hook} support via {prog_name}: {e:#?}");
+                false
+            }
+        }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fact/src/bpf/checks.rs` around lines 34 - 42, `probe_hook` is swallowing real
load errors by returning false for every failure case. Update the function to
log the underlying error from `prog.load(hook, btf)` before mapping it to
unsupported, while keeping the existing false behavior for missing programs and
wrong program types. Use the `probe_hook` function and the `Lsm` load path to
locate the change, and make sure the log clearly distinguishes load failures
from genuinely unsupported kernels.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@fact-ebpf/src/bpf/events.h`:
- Around line 193-199: The ACL copy loop in events.h is always assigning
entry.e_uid.val to args->event->acl.entries[i].e_id, but posix_acl_entry.e_uid
is only valid for ACL_USER and ACL_GROUP. Update the logic in the ACL entry
population path to branch on entry.e_tag and set e_id from e_uid.val only for
those tags; for ACL_USER_OBJ, ACL_GROUP_OBJ, ACL_MASK, and ACL_OTHER, assign
ACL_UNDEFINED_ID instead. Keep the change localized to the ACL event-building
loop so the rest of the field copying in args->event->acl.entries remains
unchanged.

---

Outside diff comments:
In `@fact/src/event/mod.rs`:
- Around line 538-555: The manual PartialEq implementation for FileData is
missing the FileData::Chown arm, so identical ownership-change events currently
fall through to false. Update the eq method in FileData’s PartialEq match to add
a Chown(this), Chown(other) branch that compares the contained values the same
way as the other variants, keeping the fallback _ => false only for truly
different variants.

---

Nitpick comments:
In `@fact/src/bpf/checks.rs`:
- Around line 34-42: `probe_hook` is swallowing real load errors by returning
false for every failure case. Update the function to log the underlying error
from `prog.load(hook, btf)` before mapping it to unsupported, while keeping the
existing false behavior for missing programs and wrong program types. Use the
`probe_hook` function and the `Lsm` load path to locate the change, and make
sure the log clearly distinguishes load failures from genuinely unsupported
kernels.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Enterprise

Run ID: 5b916de2-f11e-48d7-b345-c3ed9b660223

📥 Commits

Reviewing files that changed from the base of the PR and between 3a4a860 and 4be488a.

📒 Files selected for processing (16)
  • CHANGELOG.md
  • fact-ebpf/src/bpf/checks.c
  • fact-ebpf/src/bpf/events.h
  • fact-ebpf/src/bpf/main.c
  • fact-ebpf/src/bpf/types.h
  • fact-ebpf/src/lib.rs
  • fact/src/bpf/checks.rs
  • fact/src/bpf/mod.rs
  • fact/src/event/mod.rs
  • fact/src/metrics/kernel_metrics.rs
  • tests/event.py
  • tests/server.py
  • tests/test_acl.py
  • tests/test_xattr.py
  • tests/utils.py
  • third_party/stackrox

Comment on lines +193 to +199
for (unsigned int i = 0; i < FACT_MAX_ACL_ENTRIES && i < count; i++) {
struct posix_acl_entry entry = {0};
bpf_probe_read_kernel(&entry, sizeof(entry), &kacl->a_entries[i]);
args->event->acl.entries[i].e_tag = entry.e_tag;
args->event->acl.entries[i].e_perm = entry.e_perm;
args->event->acl.entries[i].e_id = entry.e_uid.val;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files 'fact-ebpf/src/bpf/events.h' 'fact-ebpf/src/lib.rs'
wc -l fact-ebpf/src/bpf/events.h fact-ebpf/src/lib.rs
ast-grep outline fact-ebpf/src/bpf/events.h --view expanded
ast-grep outline fact-ebpf/src/lib.rs --view expanded

Repository: stackrox/fact

Length of output: 1670


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,240p' fact-ebpf/src/bpf/events.h
printf '\n---- lib.rs ----\n'
sed -n '1,220p' fact-ebpf/src/lib.rs

Repository: stackrox/fact

Length of output: 11141


Avoid copying entry.e_uid.val for every ACL tag

posix_acl_entry.e_uid is only valid for ACL_USER / ACL_GROUP; for ACL_USER_OBJ, ACL_GROUP_OBJ, ACL_MASK, and ACL_OTHER this reads uninitialized kernel heap data and emits a bogus e_id. Use ACL_UNDEFINED_ID for the other tags.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fact-ebpf/src/bpf/events.h` around lines 193 - 199, The ACL copy loop in
events.h is always assigning entry.e_uid.val to
args->event->acl.entries[i].e_id, but posix_acl_entry.e_uid is only valid for
ACL_USER and ACL_GROUP. Update the logic in the ACL entry population path to
branch on entry.e_tag and set e_id from e_uid.val only for those tags; for
ACL_USER_OBJ, ACL_GROUP_OBJ, ACL_MASK, and ACL_OTHER, assign ACL_UNDEFINED_ID
instead. Keep the change localized to the ACL event-building loop so the rest of
the field copying in args->event->acl.entries remains unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants